﻿using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;
using System.Xml.Linq;
using EventFlowLibrary.Player;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using PerformanceUtility.ShapeRendering;
using EventFlowLibrary.LevelComponents;
using ScreenManagement;
using Nine.Animations;
using Nine.Graphics;
using LevelComponents;
using PerformanceUtility.GameDebugTools;
using System.Globalization;

namespace EventFlowLibrary.GameStructure
{
    /// <summary>
    /// Manages all event triggers in the game.
    /// TODO replace all strings with ints as the key, so we don't generate string garbage.
    ///      use "int" as the key and just keep string as an identifier for debugging.
    ///      have a "Selected value index" instead of "states.contains"
    /// </summary>
    internal sealed class GameEventManagement : DrawableGameComponent
    {
        /// <summary>
        /// Our current intersect distance
        /// </summary>
        private float? currentIntersectDistance;

        /// <summary>
        /// Our best intersection distance (closest) this round of checks, so we only check the closest event in a close group.
        /// </summary>
        //private float? bestIntersectDistance;

        /// <summary>
        /// Ray for testing character interaction.
        /// </summary>
        private Ray orientationRay = new Ray();

        /// <summary>
        /// The XML file containing all relevant text information.
        /// </summary>
        private string xmlFile;

        /// <summary>
        /// Spritebatch for drawing materials.
        /// </summary>
        private SpriteBatch spriteBatch;

        /// <summary>
        /// The dictionary for all textures.
        /// </summary>
        private Dictionary<string, Texture2D> textures;

        /// <summary>
        /// Font for the component.
        /// </summary>
        private SpriteFont spriteFont;

        /// <summary>
        /// World reference containing models, characters, and physics
        /// </summary>
        private World world;

        /// <summary>
        /// Whether GameEventAction.DisplayActionButton is triggered and we can trigger an event on subsequent input.
        /// </summary> 
        internal bool waitingOnInput = false;

        /// <summary>
        /// Prompt to display over the text when waiting for input.
        /// </summary>
        internal StringBuilder promptText = new StringBuilder(32, 32);

        /// <summary>
        /// Button to show when waiting for input.
        /// </summary>
        internal string promptButton = "A";

        /// <summary>
        /// A waiting game event if waitingOnInput is true; fired on input.
        /// </summary>
        internal List<GameEvent> promptGameEvent = new List<GameEvent>();

        /// <summary>
        /// Used to fade in the button.
        /// </summary>
        private float alphaFade = 0.0f;

        /// <summary>
        /// Fading speed for event related items such as buttons.
        /// </summary>
        private float fadeSpeed;

        /// <summary>
        /// We use a separate timer to guarantee no jitter in alpha fade from total seconds.
        /// </summary>
        private Stopwatch timer = new Stopwatch();

        /// <summary>
        /// How fast the button fades in.
        /// </summary>
        //private const float fadeRate = 500.0f;

        /// <summary>
        /// Event collision boxes.
        /// </summary>
        private List<EventBox> eventBoxes;

        /// <summary>
        /// Event collision spheres.
        /// </summary>
        private List<EventSphere> eventSpheres;

        /// <summary>
        /// Check events every 2 frames
        /// </summary>
        private bool AlternateCheckEvents = true;

        private bool collidedLastUpdate, orientedLastUpdate;

        /// <summary>
        /// Game state string reduction
        /// </summary>
        private StringBuilder gameState = new StringBuilder(256, 256);

        /// <summary>
        /// Current list of game states and values to check, in "state_value" format.
        /// Rebuilt only on event change.
        /// </summary>
        private List<string> currentGameStates = new List<string>();

        /// <summary>
        /// Create an event manager.
        /// </summary>
        /// <param name="game"></param>
        /// <param name="_XmlFile"></param>
        internal GameEventManagement(Game game, string _XmlFile, World _world)
            : base(game)
        {
            // To be loaded in LoadContent
            xmlFile = _XmlFile;

            // Track parent world
            world = _world;
        }

        /// <summary>
        /// Initialize the components.
        /// </summary>
        public override void Initialize()
        {
            eventBoxes = new List<EventBox>();
            eventSpheres = new List<EventSphere>();
            textures = new Dictionary<string, Texture2D>();

            spriteBatch = new SpriteBatch(this.Game.GraphicsDevice);

            base.Initialize();
        }

        /// <summary>
        /// Load the used content items.
        /// </summary>
        protected override void LoadContent()
        {
            // Create the content manager
            ContentManager content = new ContentManager(this.Game.Content.ServiceProvider, "Content");

            // Load the XML file
            XElement xel = XElement.Load(xmlFile);
            XmlReader reader;
            reader = xel.CreateReader();

            // Read the info
            while (reader.Read())
            {
                // Check we've found an element. Use a "switch" statement to access if we need more.
                if (reader.NodeType == XmlNodeType.Element)
                {
                    switch (reader.Name)
                    {
                        case "EventBoxes":
                            while (reader.Read())
                            {
                                // Start loading our box
                                if (reader.Name == "Box" && reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
                                {
                                    string[] min = reader["Min"].ToString().Split(',');
                                    string[] max  = reader["Max"].ToString().Split(',');
                                    string bindToModel = reader["BindToModel"].ToString();

                                    // Find which nodes we belong to.
                                    string[] nodesBelongingTo = reader["HSGNodes"].ToString().Split(',');
                                    List<HSGNode> nodes = new List<HSGNode>();
                                    if (nodesBelongingTo[0].Length > 0)
                                    {
                                        for (int i = 0; i < nodesBelongingTo.Length; i++)
                                        {
                                            for (int j = 0; j < world.levelManager.hsgNodes.Count; j++)
                                            {
                                                if (nodesBelongingTo[i] == world.levelManager.hsgNodes[j].ID)
                                                {
                                                    nodes.Add(world.levelManager.hsgNodes[j]);
                                                    continue;
                                                }
                                            }
                                        }
                                    }

                                    eventBoxes.Add(new EventBox(reader["ID"], this,
                                        new BoundingBox(new Vector3((float)Convert.ToDouble(min[0], CultureInfo.InvariantCulture), (float)Convert.ToDouble(min[1], CultureInfo.InvariantCulture), (float)Convert.ToDouble(min[2], CultureInfo.InvariantCulture)),
                                                        new Vector3((float)Convert.ToDouble(max[0], CultureInfo.InvariantCulture), (float)Convert.ToDouble(max[1], CultureInfo.InvariantCulture), (float)Convert.ToDouble(max[2], CultureInfo.InvariantCulture))), nodes));

                                    if (nodesBelongingTo[0].Length > 0)
                                    {
                                    }

                                    // Get events
                                    while (reader.Read())
                                    {
                                        if (reader.Name == "GameEvent" && reader.NodeType == XmlNodeType.Element)
                                        {
                                            GameEventType type = (GameEventType)Enum.Parse(typeof(GameEventType), reader["Type"].ToString(), true);
                                            GameEventAction action = (GameEventAction)Enum.Parse(typeof(GameEventAction), reader["Action"].ToString(), false);

                                            eventBoxes[eventBoxes.Count - 1].AddEvent(reader["State"].ToString() + "_" + reader["Value"].ToString(), new GameEvent(type, action, reader["Args"].ToString().Split(',')));
                                        }

                                        // Skip out of this set when we've reached the end
                                        if (reader.Name == "Box" && reader.NodeType == XmlNodeType.EndElement)
                                        {
                                            // If we are bound to a model, we need to let the model track the event so that we know if the model changes position
                                            // TEMP currently only works with animated models, to make all models, create parent model display type that tracks events
                                            //      and inherit all drawable models from it.
                                            if (bindToModel.Length > 0)
                                            {
                                                world.levelManager.AnimatedModels[bindToModel].AddEventBox(eventBoxes[eventBoxes.Count - 1]);
                                            }

                                            break;
                                        }
                                    }
                                }

                                // Skip out of this set when we've reached the end
                                if (reader.Name == "EventBoxes" && reader.NodeType == XmlNodeType.EndElement)
                                {
                                    break;
                                }
                            }
                            break;
                        case "EventSpheres":
                            while (reader.Read())
                            {
                                // Start loading our box
                                if (reader.Name == "Sphere" && reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
                                {
                                    string[] center = reader["Center"].ToString().Split(',');

                                    // Find which nodes we belong to.
                                    string[] nodesBelongingTo = reader["HSGNodes"].ToString().Split(',');
                                    List<HSGNode> nodes = new List<HSGNode>();
                                    if (nodesBelongingTo[0].Length > 0)
                                    {
                                        for (int i = 0; i < nodesBelongingTo.Length; i++)
                                        {
                                            for (int j = 0; j < world.levelManager.hsgNodes.Count; j++)
                                            {
                                                if (nodesBelongingTo[i] == world.levelManager.hsgNodes[j].ID)
                                                {
                                                    nodes.Add(world.levelManager.hsgNodes[j]);
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                    eventSpheres.Add(new EventSphere(reader["ID"], this,
                                        new BoundingSphere(new Vector3((float)Convert.ToDouble(center[0], CultureInfo.InvariantCulture), (float)Convert.ToDouble(center[1], CultureInfo.InvariantCulture), (float)Convert.ToDouble(center[2], CultureInfo.InvariantCulture)),
                                            (float)Convert.ToDouble(reader["Radius"].ToString(), CultureInfo.InvariantCulture)), nodes));

                                    // Get events
                                    while (reader.Read())
                                    {
                                        if (reader.Name == "GameEvent" && reader.NodeType == XmlNodeType.Element)
                                        {
                                            GameEventType type = (GameEventType)Enum.Parse(typeof(GameEventType), reader["Type"].ToString(), true);
                                            GameEventAction action = (GameEventAction)Enum.Parse(typeof(GameEventAction), reader["Action"].ToString(), false);

                                            eventSpheres[eventSpheres.Count - 1].AddEvent(reader["State"].ToString() + "_" + reader["Value"].ToString(), new GameEvent(type, action, reader["Args"].ToString().Split(',')));
                                        }

                                        // Skip out of this set when we've reached the end
                                        if (reader.Name == "Sphere" && reader.NodeType == XmlNodeType.EndElement)
                                        {
                                            break;
                                        }
                                    }
                                }

                                // Skip out of this set when we've reached the end
                                if (reader.Name == "EventSpheres" && reader.NodeType == XmlNodeType.EndElement)
                                {
                                    break;
                                }
                            }
                            break;
                    }
                }
            }

            reader.Close();

            // Add button textures
            textures.Add("A", content.Load<Texture2D>("Textures/Buttons/xboxControllerButtonA"));
            textures.Add("B", content.Load<Texture2D>("Textures/Buttons/xboxControllerButtonB"));
            textures.Add("X", content.Load<Texture2D>("Textures/Buttons/xboxControllerButtonX"));
            textures.Add("Y", content.Load<Texture2D>("Textures/Buttons/xboxControllerButtonY"));

            // Add fonts
            spriteFont = content.Load<SpriteFont>("Fonts/gamefontSmall");

            base.LoadContent();
        }

        /// <summary>
        /// Draw the visible text and textures.
        /// </summary>
        /// <param name="gameTime">Current game time.</param>
        public override void Draw(GameTime gameTime)
        {
            if (alphaFade > 0.0f)
            {
                float a;
                if (alphaFade == 1)
                {
                    if (!timer.IsRunning)
                    {
                        timer.Start();
                    }

                    a = 1 - ((float)Math.Sin(timer.Elapsed.TotalSeconds * 6) + 1) * 0.5f * alphaFade;
                }
                else
                {
                    if (timer.IsRunning)
                    {
                        timer.Stop();
                        timer.Reset();
                    }

                    a = alphaFade;
                }

                spriteBatch.Begin();

                spriteBatch.DrawString(spriteFont, promptText, new Vector2((this.Game.GraphicsDevice.Viewport.Width / 2) - spriteFont.MeasureString(promptText).X / 2, (int)(this.Game.GraphicsDevice.Viewport.Height * 0.9f - 30 + spriteFont.LineSpacing)), Color.White * alphaFade);
                spriteBatch.Draw(textures[promptButton], new Rectangle(this.Game.GraphicsDevice.Viewport.Width / 2 - 16, (int)(this.Game.GraphicsDevice.Viewport.Height * 0.9f - 32 - (spriteFont.LineSpacing / 2)), 32, 32), Color.White * a);

                spriteBatch.End();
            }

#if TRACE 
            //orientationRay.Position = world.levelManager.Character.CharacterController.Body.Position;
            //orientationRay.Direction = world.levelManager.Character.CharacterController.Body.OrientationMatrix.Forward;
            //DebugShapeRenderer.AddLine(orientationRay.Position, orientationRay.Position + orientationRay.Direction * 50, Color.Red);
            //DebugShapeRenderer.AddLine(orientationRay.Position, orientationRay.Position + world.levelManager.Character.CharacterController.Body.OrientationMatrix.Backward * 50, Color.White);
            ////DebugShapeRenderer.AddBoundingBox(world.levelManager.Character.CharacterController.Body.CollisionInformation.BoundingBox, Color.White);

            //for (int i = 0; i < eventBoxes.Count; i++)
            //{
            //    currentIntersectDistance = orientationRay.Intersects(eventBoxes[i].Box);

            //    if (currentIntersectDistance.HasValue &&
            //        currentIntersectDistance < GlobalVariables.cRayDistance)
            //    {
            //        DebugShapeRenderer.AddBoundingBox(eventBoxes[i].Box, Color.HotPink);
            //    }
            //    else
            //    {
            //        DebugShapeRenderer.AddBoundingBox(eventBoxes[i].Box, Color.CornflowerBlue);
            //    }
            //}

            //for (int i = 0; i < eventSpheres.Count; i++)
            //{
            //    currentIntersectDistance = orientationRay.Intersects(eventSpheres[i].Sphere);

            //    if (currentIntersectDistance.HasValue &&
            //        currentIntersectDistance < GlobalVariables.cRayDistance)
            //    {
            //        DebugShapeRenderer.AddBoundingSphere(eventSpheres[i].Sphere, Color.HotPink);
            //    }
            //    else
            //    {
            //        DebugShapeRenderer.AddBoundingSphere(eventSpheres[i].Sphere, Color.CornflowerBlue);
            //    }
            //}

            //DebugShapeRenderer.Draw(new GameTime(), world.levelManager.FPSCamera.ViewMatrix, world.levelManager.FPSCamera.ProjectionMatrix);
#endif

            base.Draw(gameTime);
        }

        /// <summary>
        /// Update the game.
        /// </summary>
        /// <param name="gameTime">Current game time.</param>
        public override void Update(GameTime gameTime)
        {
            if (world.textManager.CurrentEvents.Count == 0)
            {
                // Fade depending on whether we are waiting or not
                fadeSpeed = (float)gameTime.ElapsedGameTime.TotalSeconds * 3;

                if (waitingOnInput)
                {
                    alphaFade = Math.Min(alphaFade + fadeSpeed, 1);
                }
                else
                {
                    alphaFade = Math.Max(alphaFade - fadeSpeed, 0);
                }
            }
            else if (waitingOnInput)
            {
                // Make sure we don't blink/show when another text event overlays our orientation prompt.
                alphaFade = 0.0f;
                waitingOnInput = false;
                promptGameEvent.Clear();
                promptText.Remove(0, promptText.Length);
            }

            // Update events
            if (AlternateCheckEvents)
            {
                if (world.levelManager.FPSCamera.Mode != CameraMode.Manual)
                {
                    // Start measuring time for "Draw".
                    //DebugSystem.Instance.TimeRuler.BeginMark("Events", Color.Red);

                    // Can't use KeyValuePair as the enumeration changes in an event
                    CheckEvents(world.levelManager.Character.CharacterController.Body.CollisionInformation.BoundingBox, gameTime);

                    //DebugSystem.Instance.TimeRuler.EndMark("Events");
                }
            }

            // Check every two frames
            AlternateCheckEvents = !AlternateCheckEvents;

            base.Update(gameTime);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_box"></param>
        /// <param name="_gameState"></param>
        /// <param name="_gameTime"></param>
        /// <param name="_tm"></param>
        internal void CheckEvents(BoundingBox _box, GameTime _gameTime)
        {
            orientationRay.Position = world.levelManager.Character.CharacterController.Body.Position;
            orientationRay.Position.Y += 5;
            orientationRay.Direction = world.levelManager.Character.Camera.WorldMatrix.Forward;

            //bestIntersectDistance = null;

            for (int i = 0; i < eventBoxes.Count; i++)
            {
                // Retrieve our previous states
                collidedLastUpdate = eventBoxes[i].CollidedLastUpdate;
                orientedLastUpdate = eventBoxes[i].OrientedLastUpdate;
                currentIntersectDistance = orientationRay.Intersects(eventBoxes[i].Box);

                // Track our best (closest) event box
                //DebugSystem.Instance.EventDisplayer.EnqueueDisplayEvent("Ray: " + currentIntersectDistance.ToString() + "/" + bestIntersectDistance.ToString());
                //if (currentIntersectDistance.HasValue &&
                //    (!bestIntersectDistance.HasValue ||
                //    currentIntersectDistance.Value < bestIntersectDistance.Value))
                //{
                //    bestIntersectDistance = currentIntersectDistance;
                //}

                // If we have more than one node (> 0) or are cheked globally (== 0)
                if (eventBoxes[i].nodesBelongingTo.Count != 0)
                {
                    for (int k = 0; k < eventBoxes[i].nodesBelongingTo.Count; k++)
                    {
                        for (int j = 0; j < currentGameStates.Count - 1; j += 2)
                        {

                            if (eventBoxes[i].nodesBelongingTo[k].Box.Intersects(_box))
                            {
                                eventBoxes[i].currentState = currentGameStates[j];
                                eventBoxes[i].anyState = currentGameStates[j + 1];
                                eventBoxes[i].Update(_box, _gameTime, currentIntersectDistance, ref world, ref collidedLastUpdate, ref orientedLastUpdate);
                            }
                        }
                    }
                }
                else
                {
                    for (int j = 0; j < currentGameStates.Count - 1; j += 2)
                    {
                        eventBoxes[i].currentState = currentGameStates[j];
                        eventBoxes[i].anyState = currentGameStates[j + 1];
                        eventBoxes[i].Update(_box, _gameTime, currentIntersectDistance, ref world, ref collidedLastUpdate, ref orientedLastUpdate);
                    }
                }

                // We must assign these after all game states are checked, so that the "new" collisions work;
                // otherwise, the "new" collision is the first game state only.
                eventBoxes[i].CollidedLastUpdate = collidedLastUpdate;
                eventBoxes[i].OrientedLastUpdate = orientedLastUpdate;
            }

            for (int i = 0; i < eventSpheres.Count; i++)
            {
                // Retrieve our previous states
                collidedLastUpdate = eventSpheres[i].CollidedLastUpdate;
                orientedLastUpdate = eventSpheres[i].OrientedLastUpdate;
                currentIntersectDistance = orientationRay.Intersects(eventSpheres[i].Sphere);

                // Track our best (closest) event sphere
                //if (currentIntersectDistance.HasValue &&
                //    (!bestIntersectDistance.HasValue ||
                //    currentIntersectDistance.Value < bestIntersectDistance.Value))
                //{
                //    bestIntersectDistance = currentIntersectDistance;
                //}

                // If we have more than one node (> 0) or are cheked globally (== 0)
                if (eventSpheres[i].nodesBelongingTo.Count != 0)
                {
                    for (int k = 0; k < eventSpheres[i].nodesBelongingTo.Count; k++)
                    {
                        for (int j = 0; j < currentGameStates.Count - 1; j += 2)
                        {

                            if (eventSpheres[i].nodesBelongingTo[k].Box.Intersects(_box))
                            {
                                eventSpheres[i].currentState = currentGameStates[j];
                                eventSpheres[i].anyState = currentGameStates[j + 1];
                                eventSpheres[i].Update(_box, _gameTime, currentIntersectDistance, ref world, ref collidedLastUpdate, ref orientedLastUpdate);
                            }
                        }
                    }
                }
                else
                {
                    for (int j = 0; j < currentGameStates.Count - 1; j += 2)
                    {
                        eventSpheres[i].currentState = currentGameStates[j];
                        eventSpheres[i].anyState = currentGameStates[j + 1];
                        eventSpheres[i].Update(_box, _gameTime, currentIntersectDistance, ref world, ref collidedLastUpdate, ref orientedLastUpdate);
                    }
                }

                // We must assign these after all game states are checked, so that the "new" collisions work;
                // otherwise, the "new" collision is the first game state only.
                eventSpheres[i].CollidedLastUpdate = collidedLastUpdate;
                eventSpheres[i].OrientedLastUpdate = orientedLastUpdate;
            }
        }

        /// <summary>
        /// This is for checking if we are waiting on a prompt event.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="_inputState"></param>
        public void HandleInput(GameTime gameTime, InputState _inputState)
        {
            if (waitingOnInput)
            {
#if WINDOWS
                if (_inputState.CurrentKeyboardStates[GlobalVariables.playerIndexInt].IsKeyDown(Keys.A) &&
                    _inputState.LastKeyboardStates[GlobalVariables.playerIndexInt].IsKeyUp(Keys.A))
#else
                if (_inputState.CurrentGamePadStates[GlobalVariables.playerIndexInt].IsButtonDown(Buttons.A) &&
                    _inputState.LastGamePadStates[GlobalVariables.playerIndexInt].IsButtonUp(Buttons.A))
#endif
                {
                    for (int i = 0; i < promptGameEvent.Count; i++)
                    {
                        switch (promptGameEvent[i].EventAction)
                        {
                            case GameEventAction.CameraPath:
                            case GameEventAction.DequeueText:
                            case GameEventAction.DisplayActionButton:
                            default:
                                throw new NotImplementedException();
                            case GameEventAction.ChangeGameState:
                                GlobalVariables.loadVariables.gameState[promptGameEvent[i].EventArguments[0].ToString()] = promptGameEvent[i].EventArguments[1].ToString();
                                RefreshGameStates();
                                world.levelManager.ShowJournalUpdating =  Convert.ToBoolean(promptGameEvent[i].EventArguments[2], CultureInfo.InvariantCulture);;
                                break;
                            case GameEventAction.EnqueueText:
                                world.textManager.EnqueueTextEvent(promptGameEvent[i].EventArguments[0].ToString(), Convert.ToBoolean(promptGameEvent[i].EventArguments[1].ToString(), CultureInfo.InvariantCulture));
                                break;
                            case GameEventAction.LoadLevel:
                                LoadVariables loadVariables = new LoadVariables();
                                loadVariables.level = promptGameEvent[i].EventArguments[0].ToString();
                                loadVariables.cameraPitch = (float)Convert.ToDouble(promptGameEvent[i].EventArguments[1].ToString(), CultureInfo.InvariantCulture);
                                loadVariables.cameraYaw = (float)Convert.ToDouble(promptGameEvent[i].EventArguments[2].ToString(), CultureInfo.InvariantCulture);
                                loadVariables.orientation = new Quaternion((float)Convert.ToDouble(promptGameEvent[i].EventArguments[3].ToString(), CultureInfo.InvariantCulture),
                                                            (float)Convert.ToDouble(promptGameEvent[i].EventArguments[4].ToString(), CultureInfo.InvariantCulture),
                                                            (float)Convert.ToDouble(promptGameEvent[i].EventArguments[5].ToString(), CultureInfo.InvariantCulture),
                                                            (float)Convert.ToDouble(promptGameEvent[i].EventArguments[6].ToString(), CultureInfo.InvariantCulture));
                                loadVariables.position = new Vector3((float)Convert.ToDouble(promptGameEvent[i].EventArguments[7].ToString(), CultureInfo.InvariantCulture),
                                                            (float)Convert.ToDouble(promptGameEvent[i].EventArguments[8].ToString(), CultureInfo.InvariantCulture),
                                                            (float)Convert.ToDouble(promptGameEvent[i].EventArguments[9].ToString(), CultureInfo.InvariantCulture));
                                //loadVariables.saved = DateTime.FromBinary(Convert.ToInt64(world.TimePlayedAtStart));
                                loadVariables.saved = DateTime.FromFileTime(Convert.ToInt64(world.TimePlayedAtStart, CultureInfo.InvariantCulture));
                                loadVariables.timePlayed = world.TimePlayed;
                                loadVariables.totalRotationY = (float)Convert.ToDouble(promptGameEvent[i].EventArguments[10].ToString(), CultureInfo.InvariantCulture);
                                loadVariables.gameState = GlobalVariables.loadVariables.gameState;


                                // Track which models have changed positioning info
                                // Levels and model names combined are unique, so just add here.
                                Dictionary<string, Dictionary<string, ModelVariables>> movedModels = new Dictionary<string, Dictionary<string, ModelVariables>>();

                                foreach (KeyValuePair<string, StaticModel> k in world.levelManager.StaticModels)
                                {
                                    if (k.Value.HasMoved)
                                    {
                                        if (!movedModels.ContainsKey(GlobalVariables.loadVariables.level))
                                        {
                                            movedModels.Add(GlobalVariables.loadVariables.level, new Dictionary<string, ModelVariables>());
                                        }

                                        movedModels[GlobalVariables.loadVariables.level].Add(k.Key, new ModelVariables(k.Value.Position, k.Value.Rotation, k.Value.Scale));
                                    }
                                }
                                foreach (KeyValuePair<string, DynamicModel> k in world.levelManager.DynamicModels)
                                {
                                    if (k.Value.HasMoved)
                                    {
                                        if (!movedModels.ContainsKey(GlobalVariables.loadVariables.level))
                                        {
                                            movedModels.Add(GlobalVariables.loadVariables.level, new Dictionary<string, ModelVariables>());
                                        }

                                        movedModels[GlobalVariables.loadVariables.level].Add(k.Key, new ModelVariables(k.Value.Position, k.Value.Rotation, k.Value.Scale));
                                    }
                                }
                                foreach (KeyValuePair<string, AnimatedModel> k in world.levelManager.AnimatedModels)
                                {
                                    if (k.Value.HasMoved)
                                    {
                                        if (!movedModels.ContainsKey(GlobalVariables.loadVariables.level))
                                        {
                                            movedModels.Add(GlobalVariables.loadVariables.level, new Dictionary<string, ModelVariables>());
                                        }

                                        movedModels[GlobalVariables.loadVariables.level].Add(k.Key, new ModelVariables(k.Value.Position, k.Value.Rotation, k.Value.Scale));
                                    }
                                }

                                // Also, track our existing moved model data while preventing duplicates and using the latest data (moved models takes precedence over load variables)
                                foreach (KeyValuePair<string, Dictionary<string, ModelVariables>> level in GlobalVariables.loadVariables.modelPositioning)
                                {
                                    // Add level name if we don't already have it
                                    if (!movedModels.ContainsKey(level.Key))
                                    {
                                        movedModels.Add(level.Key, new Dictionary<string, ModelVariables>());
                                    }

                                    foreach (KeyValuePair<string, ModelVariables> model in GlobalVariables.loadVariables.modelPositioning[level.Key])
                                    {
                                        // Add model variables if we don't already have it
                                        if (!movedModels[level.Key].ContainsKey(model.Key))
                                        {
                                            movedModels[level.Key].Add(model.Key, new ModelVariables(model.Value.position, model.Value.rotation, model.Value.scale));
                                        }
                                    }
                                }



                                loadVariables.modelPositioning = movedModels; //GlobalVariables.loadVariables.modelPositioning;
                                LoadingScreen.Load(world.screenManager, true, world.screenManager.GetScreens()[0].ControllingPlayer.Value,
                                    new GameplayScreen(world.TimePlayed, loadVariables));
                                break;
                            case GameEventAction.Audio:
                                AudioManager.soundEffects[promptGameEvent[i].EventArguments[0].ToString()].PlaySound(1.0f, 0, 0, false);
                                break;
                            case GameEventAction.ChangeAnimation:
                                world.levelManager.Animations[promptGameEvent[i].EventArguments[0].ToString()].Play(new BoneAnimation(world.levelManager.AnimatedModels[promptGameEvent[i].EventArguments[0].ToString()].modelSkeleton,
                                                                                                                                      world.levelManager.AnimatedModels[promptGameEvent[i].EventArguments[0].ToString()].DisplayModel.GetAnimation(promptGameEvent[i].EventArguments[1].ToString())));
                                break;
                            case GameEventAction.MoveModel:
                                if (promptGameEvent[i].EventArguments[1].ToString() == "Absolute")
                                {
                                    if (world.levelManager.AnimatedModels.ContainsKey(promptGameEvent[i].EventArguments[0].ToString()))
                                    {
                                        world.levelManager.AnimatedModels[promptGameEvent[i].EventArguments[0].ToString()].SetPosition(new Vector3((float)Convert.ToDouble(promptGameEvent[i].EventArguments[2], CultureInfo.InvariantCulture), (float)Convert.ToDouble(promptGameEvent[i].EventArguments[3], CultureInfo.InvariantCulture), (float)Convert.ToDouble(promptGameEvent[i].EventArguments[4], CultureInfo.InvariantCulture)));
                                        world.levelManager.AnimatedModels[promptGameEvent[i].EventArguments[0].ToString()].SetRotation(new Vector3((float)Convert.ToDouble(promptGameEvent[i].EventArguments[5], CultureInfo.InvariantCulture), (float)Convert.ToDouble(promptGameEvent[i].EventArguments[6], CultureInfo.InvariantCulture), (float)Convert.ToDouble(promptGameEvent[i].EventArguments[7], CultureInfo.InvariantCulture)));
                                    }
                                    else if (world.levelManager.StaticModels.ContainsKey(promptGameEvent[i].EventArguments[0].ToString()))
                                    {
                                        world.levelManager.StaticModels[promptGameEvent[i].EventArguments[0].ToString()].SetPosition(new Vector3((float)Convert.ToDouble(promptGameEvent[i].EventArguments[2], CultureInfo.InvariantCulture), (float)Convert.ToDouble(promptGameEvent[i].EventArguments[3], CultureInfo.InvariantCulture), (float)Convert.ToDouble(promptGameEvent[i].EventArguments[4], CultureInfo.InvariantCulture)));
                                        world.levelManager.StaticModels[promptGameEvent[i].EventArguments[0].ToString()].SetRotation(new Vector3((float)Convert.ToDouble(promptGameEvent[i].EventArguments[5], CultureInfo.InvariantCulture), (float)Convert.ToDouble(promptGameEvent[i].EventArguments[6], CultureInfo.InvariantCulture), (float)Convert.ToDouble(promptGameEvent[i].EventArguments[7], CultureInfo.InvariantCulture)));
                                    }
                                }
                                else if (promptGameEvent[i].EventArguments[1].ToString() == "Relative")
                                {
                                    Vector3 pos = world.levelManager.AnimatedModels[promptGameEvent[i].EventArguments[0].ToString()].Position;
                                    Vector3 rot = world.levelManager.AnimatedModels[promptGameEvent[i].EventArguments[0].ToString()].Rotation;

                                    if (world.levelManager.AnimatedModels.ContainsKey(promptGameEvent[i].EventArguments[0].ToString()))
                                    {
                                        world.levelManager.AnimatedModels[promptGameEvent[i].EventArguments[0].ToString()].SetPosition(new Vector3((float)Convert.ToDouble(promptGameEvent[i].EventArguments[2], CultureInfo.InvariantCulture) + pos.X, (float)Convert.ToDouble(promptGameEvent[i].EventArguments[3], CultureInfo.InvariantCulture) + pos.Y, (float)Convert.ToDouble(promptGameEvent[i].EventArguments[4], CultureInfo.InvariantCulture) + pos.Z));
                                        world.levelManager.AnimatedModels[promptGameEvent[i].EventArguments[0].ToString()].SetRotation(new Vector3((float)Convert.ToDouble(promptGameEvent[i].EventArguments[5], CultureInfo.InvariantCulture) + rot.X, (float)Convert.ToDouble(promptGameEvent[i].EventArguments[6], CultureInfo.InvariantCulture) + rot.Y, (float)Convert.ToDouble(promptGameEvent[i].EventArguments[7], CultureInfo.InvariantCulture) + rot.Z));
                                    }
                                    else if (world.levelManager.StaticModels.ContainsKey(promptGameEvent[i].EventArguments[0].ToString()))
                                    {
                                        world.levelManager.StaticModels[promptGameEvent[i].EventArguments[0].ToString()].SetPosition(new Vector3((float)Convert.ToDouble(promptGameEvent[i].EventArguments[2], CultureInfo.InvariantCulture) + pos.X, (float)Convert.ToDouble(promptGameEvent[i].EventArguments[3], CultureInfo.InvariantCulture) + pos.Y, (float)Convert.ToDouble(promptGameEvent[i].EventArguments[4], CultureInfo.InvariantCulture) + pos.Z));
                                        world.levelManager.StaticModels[promptGameEvent[i].EventArguments[0].ToString()].SetRotation(new Vector3((float)Convert.ToDouble(promptGameEvent[i].EventArguments[5], CultureInfo.InvariantCulture) + rot.X, (float)Convert.ToDouble(promptGameEvent[i].EventArguments[6], CultureInfo.InvariantCulture) + rot.Y, (float)Convert.ToDouble(promptGameEvent[i].EventArguments[7], CultureInfo.InvariantCulture) + rot.Z));
                                    }
                                }
                                break;
                            case GameEventAction.ShowLetter:
                                world.textManager.EnqueueTextEvent(promptGameEvent[i].EventArguments[0].ToString(), false);
                                world.textManager.SetLetter(promptGameEvent[i].EventArguments[0].ToString());
                                AudioManager.soundEffects["pageflip12"].PlaySound(1.0f, 0.0f, 0.0f, false);
                                break;
                        }
                    }

                    alphaFade = 0.0f;
                    waitingOnInput = false;
                    promptGameEvent.Clear();
                }
            }
        }

        /// <summary>
        /// Add a collision based event box.
        /// </summary>
        /// <param name="_box"></param>
        public void AddEventBox(EventBox _box)
        {
            eventBoxes.Add(_box);
        }

        /// <summary>
        /// Add a collision based event sphere.
        /// </summary>
        /// <param name="_box"></param>
        public void AddEventSphere(EventSphere _sphere)
        {
            eventSpheres.Add(_sphere);
        }

        /// <summary>
        /// Creates a list of game states and their current values. Only occurs when a game state changes, to prevent string garbage.
        /// </summary>
        public void RefreshGameStates()
        {
            currentGameStates.Clear();

            foreach (string s in GlobalVariables.loadVariables.gameState.Keys.ToList<string>())
            {
                // Just assign the value here
                gameState.Remove(0, gameState.Length);
                gameState.Append(s);
                gameState.Append("_");
                gameState.Append(GlobalVariables.loadVariables.gameState[s]);
                currentGameStates.Add(gameState.ToString());

                gameState.Remove(0, gameState.Length);
                gameState.Append(s);
                gameState.Append("_*");
                currentGameStates.Add(gameState.ToString());
            }
        }
    }
}